package controllers

import (
	"context"
	"errors"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
	"gitverse.ru/synapse/kubelatte/pkg/api/v1alpha1"
	"gitverse.ru/synapse/kubelatte/pkg/logs"
	"gitverse.ru/synapse/kubelatte/pkg/operator/controllers/lease"
	"gitverse.ru/synapse/kubelatte/pkg/operator/controllers/mocks"
	"go.uber.org/zap"
	"k8s.io/apimachinery/pkg/runtime"
	controllerruntime "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sync"
	"testing"
)

func TestTriggerReconciler_updateStatus(t *testing.T) {
	var wantErr bool
	var want controllerruntime.Result
	var isAudit bool
	t.Run("changing_status", func(t *testing.T) {
		wantErr = false
		isAudit = false
		lease.SetMeLeader()
		defer func() { lease.SetMeWithoutStatus() }()
		want = controllerruntime.Result{}

		clientM := mocks.NewClient(t)
		subResourceWriterM := mocks.NewSubResourceWriter(t)

		subResourceWriterM.On("Update", mock.Anything, mock.Anything, mock.Anything).Return(nil)
		clientM.On("Status").Return(subResourceWriterM)

		trigger := v1alpha1.Trigger{
			Status: v1alpha1.TriggerStatus{Conditions: []v1alpha1.Condition{{Type: v1alpha1.LastHandledHash}}},
			Spec:   v1alpha1.TriggerSpec{MutationConfigs: []v1alpha1.MutationConfig{{Name: "some-config"}}},
		}

		r := &TriggerReconciler{
			Client:            clientM,
			deletedTriggerObj: v1alpha1.Trigger{Spec: v1alpha1.TriggerSpec{MutationConfigs: []v1alpha1.MutationConfig{{Name: "some-other-config"}}}},
		}
		got, err := r.updateStatus(context.Background(), trigger, isAudit)
		if wantErr != (err != nil) {
			t.Fatalf("error expected to be %v", wantErr)
			return
		}
		assert.Equalf(t, want, got, "updateStatus(%v, %v, %v)", context.Background(), trigger, isAudit)
	})

	t.Run("changing_status", func(t *testing.T) {
		wantErr = false
		isAudit = true
		lease.SetMeLeader()
		defer func() { lease.SetMeWithoutStatus() }()
		want = controllerruntime.Result{}

		clientM := mocks.NewClient(t)
		subResourceWriterM := mocks.NewSubResourceWriter(t)

		subResourceWriterM.On("Update", mock.Anything, mock.Anything, mock.Anything).Return(nil)
		clientM.On("Status").Return(subResourceWriterM)

		trigger := v1alpha1.Trigger{
			Status: v1alpha1.TriggerStatus{Conditions: []v1alpha1.Condition{{Type: v1alpha1.LastHandledHash}}},
			Spec:   v1alpha1.TriggerSpec{MutationConfigs: []v1alpha1.MutationConfig{{Name: "some-config"}}},
		}

		r := &TriggerReconciler{
			Client:            clientM,
			deletedTriggerObj: v1alpha1.Trigger{Spec: v1alpha1.TriggerSpec{MutationConfigs: []v1alpha1.MutationConfig{{Name: "some-other-config"}}}},
		}
		got, err := r.updateStatus(context.Background(), trigger, isAudit)
		if wantErr != (err != nil) {
			t.Fatalf("error expected to be %v", wantErr)
			return
		}
		assert.Equalf(t, want, got, "updateStatus(%v, %v, %v)", context.Background(), trigger, isAudit)
	})

	t.Run("changing_status_CheckHashInStatus_false", func(t *testing.T) {
		wantErr = false
		isAudit = false
		lease.SetMeLeader()
		defer func() { lease.SetMeWithoutStatus() }()
		want = controllerruntime.Result{}

		clientM := mocks.NewClient(t)
		subResourceWriterM := mocks.NewSubResourceWriter(t)

		subResourceWriterM.On("Update", mock.Anything, mock.Anything, mock.Anything).Return(nil)
		clientM.On("Status").Return(subResourceWriterM)

		trigger := v1alpha1.Trigger{
			Status: v1alpha1.TriggerStatus{Conditions: []v1alpha1.Condition{{Type: "status"}}},
			Spec:   v1alpha1.TriggerSpec{MutationConfigs: []v1alpha1.MutationConfig{{Name: "some-config"}}},
		}

		r := &TriggerReconciler{
			Client:            clientM,
			deletedTriggerObj: v1alpha1.Trigger{Spec: v1alpha1.TriggerSpec{MutationConfigs: []v1alpha1.MutationConfig{{Name: "some-other-config"}}}},
		}
		got, err := r.updateStatus(context.Background(), trigger, isAudit)
		if wantErr != (err != nil) {
			t.Fatalf("error expected to be %v", wantErr)
			return
		}
		assert.Equalf(t, want, got, "updateStatus(%v, %v, %v)", context.Background(), trigger, isAudit)
	})

	t.Run("changing_status_error", func(t *testing.T) {
		wantErr = true
		isAudit = false
		lease.SetMeLeader()
		defer func() { lease.SetMeWithoutStatus() }()
		want = controllerruntime.Result{}

		clientM := mocks.NewClient(t)
		subResourceWriterM := mocks.NewSubResourceWriter(t)

		subResourceWriterM.On("Update", mock.Anything, mock.Anything, mock.Anything).Return(errors.New(""))
		clientM.On("Status").Return(subResourceWriterM)

		trigger := v1alpha1.Trigger{
			Status: v1alpha1.TriggerStatus{Conditions: []v1alpha1.Condition{{Type: v1alpha1.LastHandledHash}}},
			Spec:   v1alpha1.TriggerSpec{MutationConfigs: []v1alpha1.MutationConfig{{Name: "some-config"}}},
		}

		r := &TriggerReconciler{
			Client:            clientM,
			deletedTriggerObj: v1alpha1.Trigger{Spec: v1alpha1.TriggerSpec{MutationConfigs: []v1alpha1.MutationConfig{{Name: "some-other-config"}}}},
		}
		got, err := r.updateStatus(context.Background(), trigger, isAudit)
		if wantErr != (err != nil) {
			t.Fatalf("error expected to be %v", wantErr)
			return
		}
		assert.Equalf(t, want, got, "updateStatus(%v, %v, %v)", context.Background(), trigger, isAudit)
	})

}

func TestTriggerReconciler_deferLog(t *testing.T) {
	type fields struct {
		Client            client.Client
		Scheme            *runtime.Scheme
		once              sync.Once
		triggerObj        v1alpha1.Trigger
		deletedTriggerObj v1alpha1.Trigger
		initedFlag        bool
	}
	type args struct {
		req        controllerruntime.Request
		configData []byte
		err        error
	}
	tests := []struct {
		name   string
		fields fields
		args   args
	}{
		{
			name: "err nil",
			fields: fields{
				Client:            nil,
				Scheme:            nil,
				once:              sync.Once{},
				triggerObj:        v1alpha1.Trigger{},
				deletedTriggerObj: v1alpha1.Trigger{},
				initedFlag:        false,
			},
			args: args{
				req:        controllerruntime.Request{},
				configData: nil,
				err:        nil,
			},
		},
		{
			name: "err not nil",
			fields: fields{
				Client:            nil,
				Scheme:            nil,
				once:              sync.Once{},
				triggerObj:        v1alpha1.Trigger{},
				deletedTriggerObj: v1alpha1.Trigger{},
				initedFlag:        false,
			},
			args: args{
				req:        controllerruntime.Request{},
				configData: nil,
				err:        errors.New("error"),
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			logs.Logger = zap.NewNop()
			r := &TriggerReconciler{
				Client:            tt.fields.Client,
				Scheme:            tt.fields.Scheme,
				once:              tt.fields.once,
				triggerObj:        tt.fields.triggerObj,
				deletedTriggerObj: tt.fields.deletedTriggerObj,
				initedFlag:        tt.fields.initedFlag,
			}
			r.deferLog(tt.args.req, tt.args.configData, tt.args.err)
		})
	}
}
